/*
 *
 *    Copyright (c) 2022 Project CHIP Authors
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

#include "platform/ESP32/OpenthreadLauncher.h"
#include "driver/uart.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_netif.h"
#include "esp_netif_types.h"
#include "esp_openthread.h"
#include "esp_openthread_cli.h"
#include "esp_openthread_lock.h"
#include "esp_openthread_netif_glue.h"
#include "esp_openthread_types.h"
#include "esp_vfs_eventfd.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "openthread/cli.h"
#include "openthread/instance.h"
#include "openthread/logging.h"
#include "openthread/tasklet.h"
#include <lib/core/CHIPError.h>
#include <memory>

#ifdef CONFIG_OPENTHREAD_BORDER_ROUTER
#include <esp_openthread_border_router.h>
#endif

static esp_netif_t * openthread_netif                       = nullptr;
static esp_openthread_platform_config_t * s_platform_config = nullptr;
static TaskHandle_t cli_transmit_task                       = nullptr;
static QueueHandle_t cli_transmit_task_queue                = nullptr;
static TaskHandle_t openthread_task                         = nullptr;
static constexpr uint16_t OTCLI_TRANSMIT_TASK_STACK_SIZE    = 1024;
static constexpr UBaseType_t OTCLI_TRANSMIT_TASK_PRIORITY   = 5;
static const char * TAG                                     = "OpenThread";

CHIP_ERROR cli_transmit_task_post(std::unique_ptr<char[]> && cli_str)
{
    char * cmd = cli_str.get();
    if (!cli_transmit_task_queue || xQueueSend(cli_transmit_task_queue, &cmd, portMAX_DELAY) != pdTRUE)
    {
        return CHIP_ERROR_INTERNAL;
    }
    cli_str.release();
    return CHIP_NO_ERROR;
}

static int cli_output_callback(void * context, const char * format, va_list args)
{
    int ret = 0;
    char prompt_check[3];
    vsnprintf(prompt_check, sizeof(prompt_check), format, args);
    if (!strncmp(prompt_check, "> ", sizeof(prompt_check)) && cli_transmit_task)
    {
        xTaskNotifyGive(cli_transmit_task);
    }
    else
    {
        ret = vprintf(format, args);
    }
    return ret;
}

static void esp_openthread_matter_cli_init(void)
{
    otCliInit(esp_openthread_get_instance(), cli_output_callback, NULL);
}

static void cli_transmit_worker(void * context)
{
    cli_transmit_task_queue = xQueueCreate(8, sizeof(char *));
    if (!cli_transmit_task_queue)
    {
        vTaskDelete(NULL);
        return;
    }

    while (true)
    {
        char * cmd = NULL;
        if (xQueueReceive(cli_transmit_task_queue, &cmd, portMAX_DELAY) == pdTRUE)
        {
            if (cmd)
            {
                std::unique_ptr<char[]> cmd_ptr(cmd);
                esp_openthread_cli_input(cmd_ptr.get());
            }
            else
            {
                continue;
            }
            xTaskNotifyWait(0, 0, NULL, portMAX_DELAY);
        }
    }
    vQueueDelete(cli_transmit_task_queue);
    vTaskDelete(NULL);
}

static esp_err_t cli_command_transmit_task(void)
{
    xTaskCreate(cli_transmit_worker, "otcli_trans", OTCLI_TRANSMIT_TASK_STACK_SIZE, xTaskGetCurrentTaskHandle(),
                OTCLI_TRANSMIT_TASK_PRIORITY, &cli_transmit_task);
    return ESP_OK;
}

static void cli_command_transmit_task_delete(void)
{
    if (cli_transmit_task)
    {
        vTaskDelete(cli_transmit_task);
    }
}

static esp_netif_t * init_openthread_netif(const esp_openthread_platform_config_t * config)
{
    esp_netif_config_t cfg = ESP_NETIF_DEFAULT_OPENTHREAD();
    esp_netif_t * netif    = esp_netif_new(&cfg);
    assert(netif != NULL);
    ESP_ERROR_CHECK(esp_netif_attach(netif, esp_openthread_netif_glue_init(config)));

    return netif;
}

static void ot_task_worker(void * context)
{
    // Run the main loop
    esp_openthread_launch_mainloop();

    esp_netif_destroy(openthread_netif);
    esp_openthread_netif_glue_deinit();

    esp_vfs_eventfd_unregister();
    vTaskDelete(NULL);
}

#if defined(CONFIG_OPENTHREAD_BORDER_ROUTER) && defined(CONFIG_AUTO_UPDATE_RCP)

static constexpr size_t kRcpVersionMaxSize = 100;

static void update_rcp(void)
{
    // Deinit uart to transfer UART to the serial loader
    esp_openthread_rcp_deinit();

    esp_err_t err = esp_rcp_update();
    if (err == ESP_OK)
    {
        esp_rcp_mark_image_verified(true);
    }
    else
    {
        esp_rcp_mark_image_verified(false);
    }
    esp_restart();
}

static void try_update_ot_rcp(const esp_openthread_platform_config_t * config)
{
    char internal_rcp_version[kRcpVersionMaxSize];
    const char * running_rcp_version = otPlatRadioGetVersionString(esp_openthread_get_instance());

    if (esp_rcp_load_version_in_storage(internal_rcp_version, sizeof(internal_rcp_version)) == ESP_OK)
    {
        ESP_LOGI(TAG, "Internal RCP Version: %s", internal_rcp_version);
        ESP_LOGI(TAG, "Running  RCP Version: %s", running_rcp_version);
        if (strcmp(internal_rcp_version, running_rcp_version) == 0)
        {
            esp_rcp_mark_image_verified(true);
        }
        else
        {
            update_rcp();
        }
    }
    else
    {
        ESP_LOGI(TAG, "RCP firmware not found in storage, will reboot to try next image");
        esp_rcp_mark_image_verified(false);
        esp_restart();
    }
}

static void rcp_failure_handler(void)
{
    esp_rcp_mark_image_unusable();
    char internal_rcp_version[kRcpVersionMaxSize];
    if (esp_rcp_load_version_in_storage(internal_rcp_version, sizeof(internal_rcp_version)) == ESP_OK)
    {
        ESP_LOGI(TAG, "Internal RCP Version: %s", internal_rcp_version);
        update_rcp();
    }
    else
    {
        ESP_LOGI(TAG, "RCP firmware not found in storage, will reboot to try next image");
        esp_rcp_mark_image_verified(false);
        esp_restart();
    }
}
#endif // CONFIG_OPENTHREAD_BORDER_ROUTER && CONFIG_AUTO_UPDATE_RCP

esp_err_t set_openthread_platform_config(esp_openthread_platform_config_t * config)
{
    if (!s_platform_config)
    {
        s_platform_config = (esp_openthread_platform_config_t *) malloc(sizeof(esp_openthread_platform_config_t));
        if (!s_platform_config)
        {
            return ESP_ERR_NO_MEM;
        }
    }
    memcpy(s_platform_config, config, sizeof(esp_openthread_platform_config_t));
    return ESP_OK;
}

#if defined(CONFIG_OPENTHREAD_BORDER_ROUTER) && defined(CONFIG_AUTO_UPDATE_RCP)
esp_err_t openthread_init_br_rcp(const esp_rcp_update_config_t * update_config)
{
    esp_err_t err = ESP_OK;
    if (update_config)
    {
        err = esp_rcp_update_init(update_config);
    }
    esp_openthread_register_rcp_failure_handler(rcp_failure_handler);
    return err;
}
#endif // CONFIG_OPENTHREAD_BORDER_ROUTER && CONFIG_AUTO_UPDATE_RCP

esp_err_t openthread_init_stack(void)
{
    // Used eventfds:
    // * netif
    // * ot task queue
    // * radio driver
    esp_vfs_eventfd_config_t eventfd_config = {
        .max_fds = 3,
    };

    ESP_ERROR_CHECK(esp_netif_init());
    esp_err_t err = esp_vfs_eventfd_register(&eventfd_config);
    if (err == ESP_ERR_INVALID_STATE)
    {
        ESP_LOGW(TAG, "eventfd is already registered");
    }
    else if (err != ESP_OK)
    {
        return err;
    }
    assert(s_platform_config);
    // Initialize the OpenThread stack
    if ((err = esp_openthread_init(s_platform_config)) == ESP_ERR_INVALID_STATE)
    {
        ESP_LOGW(TAG, "OpenThread is already initialized");
        return ESP_OK;
    }
    ESP_ERROR_CHECK(err);
#if defined(CONFIG_OPENTHREAD_BORDER_ROUTER) && defined(CONFIG_AUTO_UPDATE_RCP)
    try_update_ot_rcp(s_platform_config);
#endif // CONFIG_OPENTHREAD_BORDER_ROUTER && CONFIG_AUTO_UPDATE_RCP
#ifdef CONFIG_OPENTHREAD_CLI
    esp_openthread_matter_cli_init();
    cli_command_transmit_task();
#endif
    // Initialize the esp_netif bindings
    openthread_netif = init_openthread_netif(s_platform_config);
    return ESP_OK;
}

esp_err_t openthread_launch_task(void)
{
    xTaskCreate(ot_task_worker, "ot_task", CONFIG_THREAD_TASK_STACK_SIZE, xTaskGetCurrentTaskHandle(), 5, &openthread_task);
    return ESP_OK;
}

esp_err_t openthread_deinit_stack(void)
{
    esp_openthread_netif_glue_deinit();
    esp_netif_destroy(openthread_netif);
#ifdef CONFIG_OPENTHREAD_CLI
    cli_command_transmit_task_delete();
#endif
    return esp_openthread_deinit();
}

void openthread_delete_task(void)
{
    if (openthread_task)
    {
        vTaskDelete(openthread_task);
    }
}
